Author

DaaniiH

Published

June 1, 2025

1 Libraries

Code anzeigen
library(readr)
library(readxl)
library(pxmake)   # Load PX files
library(pxR)      # Load PX files
library(jsonlite) # Load JSON files

library(writexl)  # Write Excel files

library(dplyr)
library(tidyverse)
library(ggplot2)  # Diagrams
library(ggforce)  # Diagrams
library(DT)       # datatable()

library(DescTools)

2 Initial data load

2.1 Lookup tables

2.1.1 Parteinamen

Code anzeigen
# Dataframe bilden aus zwei Vektoren
parteinamen <- data.frame(
  kuerzel = c("fdp", "sps", "svp", "mitte", "evp", "gps", "glp", "ucsp", "pda", "sd", "edu", "fps", "lega", "kvp", "mcg", "cvp", "bdp", "lps", "ldu", "poch", "rep", "eco", "sgv", "sbv", "sgb", "travs", "sav", "vsa", "vpod", "voev", "tcs", "vcs", "acs", "sbk", "ssv", "gem", "kdk", "kkjpd", "gdk", "ldk"),
  
  name = c("Freisinnig-demokratische Partei (FDP.Die Liberalen)", "Sozialdemokratische Partei", "Schweizerische Volkspartei (bis 1936 Parolen der BGB Bern)", "Die Mitte", "Evangelische Volkspartei", "Grüne Partei der Schweiz", "Grünliberale Partei", "Christlichsoziale Partei der Schweiz (von der CVP unabhängige CSP)", "Partei der Arbeit", "Schweizer Demokraten", "Eidgenössisch-Demokratische Union", "Autopartei", "Lega dei Ticinesi", "Katholische Volkspartei", "Mouvement Citoyens Genevois", "Christlichdemokratische Volkspartei", "Bürgerlich-Demokratische Partei", "Liberale Partei der Schweiz", "Landesring der Unabhängigen", "Progressive Organisationen der Schweiz", "Schweizerische Republikanische Bewegung", "Economiesuisse (bis 15.9.2000: Schweizerischer Handels- und Industrieverein SHIV (Vorort))", "Schweizerischer Gewerbeverband", "Schweizer Bauernverband", "Schweizerischer Gewerkschaftsbund", "Travail.Suisse (bis 2002: Parolen des Christlichnationalen Gewerkschaftsbunds (CNG); dieser fusionierte per 1.1.2003 mit der VSA zu Travail.Suisse)", "Schweizerischer Arbeitgeberverband (bis 1996: Zentralverband Schweizerischer Arbeitgeber-Organisationen ZSAO)", "Vereinigung schweizerischer Angestelltenverbände", "Verband des Personals öffentlicher Dienste", "Verband öffentlicher Verkehr", "Touring Club Schweiz", "Verkehrs-Club der Schweiz", "Automobil Club der Schweiz", "Schweizer Bischofskonferenz", "Schweizerischer Städteverband", "Schweizerischer Gemeindeverband", "Konferenz der Kantonsregierungen", "Konferenz der kantonalen Justiz- und Polizeidirektoren", "Schweizerische Gesundheitsdirektorenkonferenz", "Konferenz der kantonalen Landwirtschaftsdirektoren"),
  stringsAsFactors = FALSE)

# Neue Spalte mit "p-" Präfix für späteren Lookup der Parteiparolen
parteinamen$p_kuerzel <- paste0("p-", parteinamen$kuerzel) 

# Parteikürzel in Grossbuchstaben 
parteinamen$kuerzel <-  toupper(parteinamen$kuerzel)

# Spaltenreihenfolge anpassen: p_kuerzel, kuerzel, name
parteinamen <- parteinamen[, c("p_kuerzel", "kuerzel", "name")]

# Anzeige mit datatable()
datatable(parteinamen,
          filter = 'top',
          options = list(pageLength = 7,
          search = list(regex = TRUE,
                        caseInsensitive = TRUE)))

2.2 Abstimmungen

Code anzeigen
# Schweizweite Abstimmungen
voting_raw <- read_delim("~/CAS/Zertifikatsarbeit/data/votes/abstimmungen_swissvotes_DATASET CSV 09-02-2025.csv",
                     delim = ";",
                     escape_double = FALSE,
                     trim_ws = TRUE,
                     show_col_types = FALSE)

# TODO evt. nur random sample berechnen auf grund Grösse...
datatable(voting_raw,
          filter = 'top',
          options = list(pageLength = 7,
          search = list(regex = TRUE,
                        caseInsensitive = TRUE)))

2.3 Wahlen

2.3.1 Bundesebene

2.3.1.1 Nationalrat

Code anzeigen
# # PX-Datei einlesen
# elec_nationalrat_px <- read.px("~/CAS/Zertifikatsarbeit/data/elections/px-x-1702020000_103_BUND_NATIONALRAT.px")
# 
# # View PX Format
# str(elec_nationalrat_px)
# 
# # PX-Objekt in Dataframe umwandeln
# elec_nationalrat <- as.data.frame(elec_nationalrat_px)


# Nationalrat: Daten einlesen
elec_nationalrat  <- read_excel("~/CAS/Zertifikatsarbeit/data/elections/Ratsmitglieder_1848_DE_BUND_NR.xlsx")

str(elec_nationalrat)
tibble [492 × 21] (S3: tbl_df/tbl/data.frame)
 $ Active               : logi [1:492] TRUE FALSE FALSE TRUE TRUE FALSE ...
 $ FirstName            : chr [1:492] "Jean-Luc" "Jean-Luc" "Jean-Luc" "Cyril" ...
 $ LastName             : chr [1:492] "Addor" "Addor" "Addor" "Aellen" ...
 $ GenderAsString       : chr [1:492] "m" "m" "m" "m" ...
 $ CantonName           : chr [1:492] "Wallis" "Wallis" "Wallis" "Genf" ...
 $ CantonAbbreviation   : chr [1:492] "VS" "VS" "VS" "GE" ...
 $ CouncilName          : chr [1:492] "Nationalrat" "Nationalrat" "Nationalrat" "Nationalrat" ...
 $ ParlGroupName        : chr [1:492] "Fraktion der Schweizerischen Volkspartei" "Fraktion der Schweizerischen Volkspartei" "Fraktion der Schweizerischen Volkspartei" "FDP-Liberale Fraktion" ...
 $ ParlGroupAbbreviation: chr [1:492] "V" "V" "V" "RL" ...
 $ PartyName            : chr [1:492] "Schweizerische Volkspartei" "Schweizerische Volkspartei" "Schweizerische Volkspartei" "FDP.Die Liberalen" ...
 $ PartyAbbreviation    : chr [1:492] "SVP" "SVP" "SVP" "FDP-Liberale" ...
 $ MaritalStatusText    : chr [1:492] "verheiratet" "verheiratet" "verheiratet" NA ...
 $ Nationality          : chr [1:492] "Schweiz,Italien" "Schweiz,Italien" "Schweiz,Italien" "Schweiz" ...
 $ BirthPlace_City      : chr [1:492] "Lausanne" "Lausanne" "Lausanne" "Genf" ...
 $ BirthPlace_Canton    : chr [1:492] "Waadt" "Waadt" "Waadt" "Genf" ...
 $ Mandates             : chr [1:492] "Vice-président de l'UDC du Valais romand; Membre du Comité central de l'UDC Suisse; Député au Grand Conseil: 20"| __truncated__ "Vice-président de l'UDC du Valais romand; Membre du Comité central de l'UDC Suisse; Député au Grand Conseil: 20"| __truncated__ "Vice-président de l'UDC du Valais romand; Membre du Comité central de l'UDC Suisse; Député au Grand Conseil: 20"| __truncated__ "PLR Genève" ...
 $ DateJoining          : chr [1:492] "04.12.2023" "30.11.2015" "02.12.2019" "04.12.2023" ...
 $ DateLeaving          : chr [1:492] NA "01.12.2019" "03.12.2023" NA ...
 $ Citizenship          : chr [1:492] "Sainte-Croix (VD),Savièse (VS)" "Sainte-Croix (VD),Savièse (VS)" "Sainte-Croix (VD),Savièse (VS)" "Genf (GE)" ...
 $ DateOfBirth          : chr [1:492] "22.04.1964" "22.04.1964" "22.04.1964" "29.02.1972" ...
 $ DateOfDeath          : logi [1:492] NA NA NA NA NA NA ...

2.3.1.2 Ständerat

Code anzeigen
# Ständerat: Daten einlesen
elec_ständerat  <- read_excel("~/CAS/Zertifikatsarbeit/data/elections/Ratsmitglieder_1848_DE_BUND_SR.xlsx")

str(elec_ständerat)
tibble [155 × 21] (S3: tbl_df/tbl/data.frame)
 $ Active               : logi [1:155] TRUE FALSE TRUE FALSE FALSE FALSE ...
 $ FirstName            : chr [1:155] "Marianne" "Marianne" "Pirmin" "Pirmin" ...
 $ LastName             : chr [1:155] "Binder-Keller" "Binder-Keller" "Bischof" "Bischof" ...
 $ GenderAsString       : chr [1:155] "f" "f" "m" "m" ...
 $ CantonName           : chr [1:155] "Aargau" "Aargau" "Solothurn" "Solothurn" ...
 $ CantonAbbreviation   : chr [1:155] "AG" "AG" "SO" "SO" ...
 $ CouncilName          : chr [1:155] "Ständerat" "Nationalrat" "Ständerat" "Nationalrat" ...
 $ ParlGroupName        : chr [1:155] "Die Mitte-Fraktion. Die Mitte. EVP." "Die Mitte-Fraktion. Die Mitte. EVP." "Die Mitte-Fraktion. Die Mitte. EVP." "Fraktion CVP/EVP/glp" ...
 $ ParlGroupAbbreviation: chr [1:155] "M-E" "M-E" "M-E" "M-E" ...
 $ PartyName            : chr [1:155] "Die Mitte" "Christlichdemokratische Volkspartei der Schweiz" "Die Mitte" "Christlichdemokratische Volkspartei der Schweiz" ...
 $ PartyAbbreviation    : chr [1:155] "M-E" "CVP" "M-E" "CVP" ...
 $ MaritalStatusText    : chr [1:155] "verheiratet" "verheiratet" NA NA ...
 $ Nationality          : chr [1:155] "Schweiz" "Schweiz" "Schweiz" "Schweiz" ...
 $ BirthPlace_City      : chr [1:155] "Zürich" "Zürich" "Solothurn" "Solothurn" ...
 $ BirthPlace_Canton    : chr [1:155] "Zürich" "Zürich" "Solothurn" "Solothurn" ...
 $ Mandates             : chr [1:155] "Mitglied Präsidium Die Mitte Schweiz; Präsidentin Die Mitte Aargau" "Mitglied Präsidium Die Mitte Schweiz; Präsidentin Die Mitte Aargau" "Legislative des Kantons (Kantonsrat): von April 2005 bis November 2007; Exekutive der Gemeinde (Gemeinderat) So"| __truncated__ "Legislative des Kantons (Kantonsrat): von April 2005 bis November 2007; Exekutive der Gemeinde (Gemeinderat) So"| __truncated__ ...
 $ DateJoining          : chr [1:155] "04.12.2023" "02.12.2019" "04.12.2023" "03.12.2007" ...
 $ DateLeaving          : chr [1:155] NA "03.12.2023" NA "04.12.2011" ...
 $ Citizenship          : chr [1:155] "Baden (AG),Untersiggenthal (AG),Zurzach (Bad Zurzach) (AG)" "Baden (AG),Untersiggenthal (AG),Zurzach (Bad Zurzach) (AG)" "Eggersriet (SG),Grub (AR)" "Eggersriet (SG),Grub (AR)" ...
 $ DateOfBirth          : chr [1:155] "15.06.1958" "15.06.1958" "24.02.1959" "24.02.1959" ...
 $ DateOfDeath          : logi [1:155] NA NA NA NA NA NA ...

2.3.2 Kantonsebene

2.3.2.1 Kantonale Parlamente (Legislative)

Code anzeigen
# TODO Die Kantonalen Abstimmungen finden nicht in allen kantonen gleichzeitg statt. Deshalb genauer die Räte/Konstellation zum Zeitpunkt der jeweiligen Abstimmung zu prüfen.


# Struktur der "schön formatierten" Exceldatei lässt keinen "simplen" Import zu.
# Header ist in Zeile 2 und nicht vollständig
# Daten (für Kantone) starten in Zeile 4 aber enden auf Zeile 29 bevor es mit Kommentaren und Fussnoten weitergeht 


# Header aus Zeile 2 für Spaltennamen lesen
tmp_header <- read_excel("~/CAS/Zertifikatsarbeit/data/elections/je-d-17.02.05.01.03_KANTON_Kantonale_Parlamentswahlen.xlsx", 
                     sheet = 1, 
                     skip = 1,   # Header ist Zeile 2
                     n_max = 0) %>%
  names()

# Lücken im Header anpassen (i.e. erste beiden Spalten benennen)
tmp_header <- c("Kanton", "Leer", tmp_header)

tmp_name_map <- c("Kanton"        = "Kanton",
                  "Leer"          = "Leer",
                  "Wahljahr 5)"   = "Wahljahr",
                  "FDP 2)"        = "FDP",
                  "SP"            = "SP",
                  "SVP"           = "SVP",
                  "LPS 2)"        = "LPS",
                  "EVP"           = "EVP",
                  "CSP"           = "CSP",
                  "GLP"           = "GLP",
                  "Die Mitte 8)"  = "Mitte",
                  "CVP 3) 8)"     = "CVP",
                  "BDP 8)"        = "BDP",
                  "PdA"           = "PdA",
                  "PSA"           = "PSA",
                  "Grüne 9)"      = "Grüne",
                  "FGA"           = "FGA",
                  "Sol."          = "Sol.",
                  "EDU"           = "EDU",
                  "Lega"          = "Lega",
                  "MCG (MCR)"     = "MCR",
                  "Übrige 4)"     = "Übrige",
                  "Total"         = "Total")



# Automatisch ersetzen
tmp_header <- ifelse(tmp_header %in% names(tmp_name_map),
                     tmp_name_map[tmp_header],
                     tmp_header)

print(tmp_header)
 [1] "Kanton"   "Leer"     "Wahljahr" "FDP"      "SP"       "SVP"     
 [7] "LPS"      "EVP"      "CSP"      "GLP"      "Mitte"    "CVP"     
[13] "BDP"      "PdA"      "PSA"      "Grüne"    "FGA"      "Sol."    
[19] "EDU"      "Lega"     "MCR"      "Übrige"   "Total"   
Code anzeigen
# Daten ab Zeile 4 importieren
elec_kantonsparlament_raw <- read_excel("~/CAS/Zertifikatsarbeit/data/elections/je-d-17.02.05.01.03_KANTON_Kantonale_Parlamentswahlen.xlsx", 
                 sheet = 1, 
                 skip = 3,         # überspringt die ersten 3 Zeilen
                 col_names = tmp_header)

# Schritt 4: Nur Zeilen behalten, in denen "Wahljahr" nicht NA ist
elec_kantonsparlament <- elec_kantonsparlament_raw  %>%
  filter(!is.na(Wahljahr)) %>% 
  select(-Leer)

# Ergebnis anzeigen
print(elec_kantonsparlament)
# A tibble: 26 × 22
   Kanton   Wahljahr FDP   SP    SVP   LPS   EVP   CSP   GLP   Mitte CVP   BDP  
   <chr>       <dbl> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
 1 Zürich       2023 29    36    46    *     7     *     24    11    *     *    
 2 Bern         2022 18    32    44    *     9     *     16    12    *     *    
 3 Luzern       2023 22    19    27    *     0     *     8     32    *     *    
 4 Uri 6)       2024 12    4     17    *     *     *     3     22    *     *    
 5 Schwyz       2024 19    14    38    *     0     *     5     23    *     *    
 6 Obwalden     2022 11    6     13    *     *     *     2     19    *     *    
 7 Nidwald…     2022 16    2     15    *     *     *     5     15    *     *    
 8 Glarus       2022 11    8     18    *     *     *     3     12    *     *    
 9 Zug          2022 18    8     18    *     *     *     6     19    *     *    
10 Freiburg     2021 23    21    18    *     0     4     3     26    *     *    
# ℹ 16 more rows
# ℹ 10 more variables: PdA <chr>, PSA <chr>, Grüne <chr>, FGA <chr>,
#   Sol. <chr>, EDU <chr>, Lega <chr>, MCR <chr>, Übrige <chr>, Total <dbl>

2.3.2.2 Kantonale Regierung (Exekutive)

Code anzeigen
# TODO Die Kantonalen Abstimmungen finden nicht in allen kantonen gleichzeitg statt. Deshalb genauer die Räte/Konstellation zum Zeitpunkt der jeweiligen Abstimmung zu prüfen.


# Struktur der "schön formatierten" Exceldatei lässt keinen "simplen" Import zu.
# Header ist in Zeile 2 und nicht vollständig
# Daten (für Kantone) starten in Zeile 4 aber enden auf Zeile 29 bevor es mit Kommentaren und Fussnoten weitergeht 


# Header aus Zeile 2 für Spaltennamen lesen
tmp_header <- read_excel("~/CAS/Zertifikatsarbeit/data/elections/je-d-17.02.06.01_KANTON_Kantonale_Regierungswahlen.xlsx", 
                     sheet = 1, 
                     skip = 1,   # Header ist Zeile 2
                     n_max = 0) %>%
  names()

# Lücken im Header anpassen (i.e. erste beiden Spalten benennen)
tmp_header <- c("Kanton", tmp_header)

tmp_name_map <- c("Kanton"        = "Kanton",
                  "Wahljahr 1"    = "Wahljahr",
                  "FDP 2"         = "FDP",
                  "SP"            = "SP",
                  "SVP"           = "SVP",
                  "LP 2"          = "LPS",
                  "EVP"           = "EVP",
                  "CSP"           = "CSP",
                  "GLP"           = "GLP",
                  "Die Mitte 3"   = "Mitte",
                  "CVP 3"         = "CVP",
                  "BDP 3"         = "BDP",
                  "PdA"           = "PdA",
                  "PSA"           = "PSA",
                  "Grüne 5"       = "Grüne",
                  "FGA"           = "FGA",
                  "Sol."          = "Sol.",
                  "Lega"          = "Lega",
                  "MCG (MCR)"     = "MCR",
                  "Übrige 4"      = "Übrige",
                  "Total"         = "Total")

# Automatisch ersetzen
tmp_header <- ifelse(tmp_header %in% names(tmp_name_map),
                     tmp_name_map[tmp_header],
                     tmp_header)

print(tmp_header)
 [1] "Kanton"   "Wahljahr" "FDP"      "SP"       "SVP"      "LPS"     
 [7] "EVP"      "CSP"      "GLP"      "Mitte"    "CVP"      "BDP"     
[13] "PdA"      "PSA"      "Grüne"    "FGA"      "Sol."     "Lega"    
[19] "MCR"      "Übrige"   "Total"   
Code anzeigen
# Daten ab Zeile 4 importieren
elec_kantonsregierung_raw <- read_excel("~/CAS/Zertifikatsarbeit/data/elections/je-d-17.02.06.01_KANTON_Kantonale_Regierungswahlen.xlsx", 
                 sheet = 1, 
                 skip = 3,         # überspringt die ersten 3 Zeilen
                 col_names = tmp_header)

# Schritt 4: Nur Zeilen behalten, in denen "Wahljahr" nicht NA ist
elec_kantonsregierung <- elec_kantonsregierung_raw  %>%
  filter(!is.na(Wahljahr)) 


# Ergebnis anzeigen
print(elec_kantonsparlament)
# A tibble: 26 × 22
   Kanton   Wahljahr FDP   SP    SVP   LPS   EVP   CSP   GLP   Mitte CVP   BDP  
   <chr>       <dbl> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
 1 Zürich       2023 29    36    46    *     7     *     24    11    *     *    
 2 Bern         2022 18    32    44    *     9     *     16    12    *     *    
 3 Luzern       2023 22    19    27    *     0     *     8     32    *     *    
 4 Uri 6)       2024 12    4     17    *     *     *     3     22    *     *    
 5 Schwyz       2024 19    14    38    *     0     *     5     23    *     *    
 6 Obwalden     2022 11    6     13    *     *     *     2     19    *     *    
 7 Nidwald…     2022 16    2     15    *     *     *     5     15    *     *    
 8 Glarus       2022 11    8     18    *     *     *     3     12    *     *    
 9 Zug          2022 18    8     18    *     *     *     6     19    *     *    
10 Freiburg     2021 23    21    18    *     0     4     3     26    *     *    
# ℹ 16 more rows
# ℹ 10 more variables: PdA <chr>, PSA <chr>, Grüne <chr>, FGA <chr>,
#   Sol. <chr>, EDU <chr>, Lega <chr>, MCR <chr>, Übrige <chr>, Total <dbl>

2.4 Parteilandschaft

Code anzeigen
parties_rating <- read_excel("~/CAS/Zertifikatsarbeit/data/parties_economic_socio-political_rating.xlsx",
                             sheet = "matrix_eco-socio_enhanced")

3 Transformation & visualization

3.1 Abstimmungen

Code anzeigen
voting <- voting_raw %>%
  mutate(datum = as.Date(datum, format = "%d.%m.%Y")) %>%  # Format-Umwandlung
  filter(datum >= as.Date("2019-12-31")) %>%               # Vergleich
  select(anr,                                              # Spalten-Wahl
         datum,
         titel_kurz_d,
         titel_off_d,
         rechtsform, #1 Obligatorisches Referendum
                     #2 Fakultatives Referendum
                     #3 Volksinitiative
                     #4 Direkter Gegenentwurf zu einer Volksinitiative
                     #5 Stichfrage
         dep,
         `br-pos`,   #1 Befürwortend
                     #2 Ablehnend
                     #3 Keine
                     #8 Vorzug für den Gegenentwurf (bei Stichfragen)
                     #9 Vorzug für die Volksinitiative (bei Stichfragen)
                     #. Missing
         legisjahr,
         `pa-iv`,
         `bv-pos`,   #1 Befürwortend
                     #2 Ablehnend
                     #3 Keine Abstimmungsempfehlung des Parlaments
                     #8 Vorzug für den Gegenentwurf (bei Stichfragen)
         `nr-pos`,   #1 Befürwortende Mehrheit im Nationalrat
                     #2 Ablehnende Mehrheit im Nationalrat
                     #3 Keine Abstimmungsempfehlung des Nationalrats
                     #8 Vorzug für den Gegenentwurf (bei Stichfragen)
         nrja,
         nrnein,
         `sr-pos`,   #1 Befürwortende Mehrheit im Ständerat
                     #2 Ablehnende Mehrheit im Ständerat
                     #3 Keine Abstimmungsempfehlung des Ständerats
                     #8 Vorzug für den Gegenentwurf (bei Stichfragen)
         srja,
         srnein,
         starts_with("p-"),
                     #1 Ja-Parole
                     #2 Nein-Parole
                     #3 keine Parole abzugeben
                     #4 empfahl, einen leeren Stimmzettel einzulegen
                     #5 Stimmfreigabe
                     #8 Bevorzugung des Gegenentwurfs (bei Stichfrage)
                     #9 Bevorzugung der Volksinitiative (bei Stichfrage)
                     #66 Neutral: keine Parole oder Empfehlung auf leer einlegen                        #9999 Organisation existiert nicht
                     #. Unbekannt
         volk,       #0 Eine Mehrheit der Abstimmenden hat die Vorlage abgelehnt
                     #1 Eine Mehrheit der Abstimmenden hat die Vorlage angenommen
                     #8 Bei Stichfragen:  Mehrheit für Gegenentwurfs
                     #9 Bei Stichfragen:  Mehrheit Volksinitiative
         stand,
                     #0 Die Vorlage hat keine Mehrheit der Standesstimmen er-reicht
                     #1 Die Vorlage hat die Mehrheit der Standesstimmen erreicht
                     #3 Ständemehr nicht notwendig
                     #8 Bei Stichfragen: Mehrheit für Gegenentwurf
                     #9 Bei Stichfragen: Mehrheit für Volksinitiative
         annahme,    #0 Ablehnung der Vorlage
                     #1 Annahme der Vorlage
                     #8 Bei Stichfragen: Gegenentwurf angenommen
                     #9 Bei Stichfragen: Volksinitiative angenommen
                     #. Bei Stichfragen: Ergebnis der Stichfrage obsolet
         berecht,
         stimmen,
         bet,
         leer,
         ungultig,
         gultig,
         volkja,
         volknein,
         `volkja-proz`
         
  )
Code anzeigen
grep("^p-", names(voting), value = TRUE)
 [1] "p-fdp"                "p-sps"                "p-svp"               
 [4] "p-mitte"              "p-evp"                "p-gps"               
 [7] "p-glp"                "p-pda"                "p-sd"                
[10] "p-edu"                "p-fps"                "p-lega"              
[13] "p-kvp"                "p-mcg"                "p-ucsp"              
[16] "p-cvp"                "p-bdp"                "p-lps"               
[19] "p-ldu"                "p-poch"               "p-rep"               
[22] "p-eco"                "p-sgv"                "p-sbv"               
[25] "p-sgb"                "p-travs"              "p-sav"               
[28] "p-vsa"                "p-vpod"               "p-voev"              
[31] "p-tcs"                "p-vcs"                "p-acs"               
[34] "p-sbk"                "p-ssv"                "p-gem"               
[37] "p-kdk"                "p-kkjpd"              "p-gdk"               
[40] "p-ldk"                "p-vdk"                "p-sodk"              
[43] "p-endk"               "p-fdk"                "p-edk"               
[46] "p-bpuk"               "p-others_yes"         "p-others_yes-fr"     
[49] "p-others_no"          "p-others_no-fr"       "p-others_free"       
[52] "p-others_free-fr"     "p-others_counterp"    "p-others_counterp-fr"
[55] "p-others_init"        "p-others_init-fr"    
Code anzeigen
paste(grep("^p-", names(voting), value = TRUE), collapse = ", ")
[1] "p-fdp, p-sps, p-svp, p-mitte, p-evp, p-gps, p-glp, p-pda, p-sd, p-edu, p-fps, p-lega, p-kvp, p-mcg, p-ucsp, p-cvp, p-bdp, p-lps, p-ldu, p-poch, p-rep, p-eco, p-sgv, p-sbv, p-sgb, p-travs, p-sav, p-vsa, p-vpod, p-voev, p-tcs, p-vcs, p-acs, p-sbk, p-ssv, p-gem, p-kdk, p-kkjpd, p-gdk, p-ldk, p-vdk, p-sodk, p-endk, p-fdk, p-edk, p-bpuk, p-others_yes, p-others_yes-fr, p-others_no, p-others_no-fr, p-others_free, p-others_free-fr, p-others_counterp, p-others_counterp-fr, p-others_init, p-others_init-fr"

3.2 Wahlen

3.2.1 Bundesebene

3.2.1.1 Nationalrat

Code anzeigen
#TODO Es kann innerhalb der Legislatur Wechsel geben --> Anstelle der heute "Aktiven" ist es deshalb genauer die Räte/Konstellation zum Zeitpunkt der jeweiligen Abstimmung zu prüfen. 

# Anzeige mit datatable()
datatable(elec_nationalrat,
          filter = 'top',
          options = list(pageLength = 7,
          search = list(regex = TRUE,
                        caseInsensitive = TRUE)))
Code anzeigen
# Fehlende Werte (NA), Klassen und Levels prüfen
Abstract(elec_nationalrat)
────────────────────────────────────────────────────────────────────────────── 
elec_nationalrat

data frame: 492 obs. of  21 variables
        0 complete cases (0.0%)

  Nr  Class  ColName                NAs           Levels
  1   log    Active                   .                 
  2   chr    FirstName                .                 
  3   chr    LastName                 .                 
  4   chr    GenderAsString           .                 
  5   chr    CantonName               .                 
  6   chr    CantonAbbreviation       .                 
  7   chr    CouncilName              .                 
  8   chr    ParlGroupName            2 (0.4%)          
  9   chr    ParlGroupAbbreviation    2 (0.4%)          
  10  chr    PartyName                .                 
  11  chr    PartyAbbreviation        .                 
  12  chr    MaritalStatusText      324 (65.9%)         
  13  chr    Nationality              1 (0.2%)          
  14  chr    BirthPlace_City          2 (0.4%)          
  15  chr    BirthPlace_Canton       17 (3.5%)          
  16  chr    Mandates                47 (9.6%)          
  17  chr    DateJoining              .                 
  18  chr    DateLeaving            204 (41.5%)         
  19  chr    Citizenship              4 (0.8%)          
  20  chr    DateOfBirth              .                 
  21  log    DateOfDeath            492 (100.0%)        
Code anzeigen
PlotMiss(elec_nationalrat)

Code anzeigen
# Datensatz reduzieren auf Aktive und doppelte Einträge elimnieren
elec_nationalrat_reduced <- elec_nationalrat %>%
  filter(Active == TRUE) %>% 
  distinct(.keep_all = TRUE) %>% 
  select(CantonAbbreviation,
         CantonName,
         PartyAbbreviation,
         PartyName) %>% 
  mutate(value = 1)

datatable(elec_nationalrat_reduced,
          filter = 'top',
          options = list(pageLength = 7,
          search = list(regex = TRUE,
                        caseInsensitive = TRUE)))
Code anzeigen
# Sitze pro Kanton, Partei und Jahr 
    
    # long format
      df_sum <- elec_nationalrat_reduced %>%
      group_by(CantonAbbreviation, PartyAbbreviation) %>%
      summarise(sum_value = sum(value, na.rm = TRUE),
                .groups = "drop")
    
    # wide format
    # Pivotieren: Partei-Spalten erzeugen
    df_wide <- df_sum %>%
      pivot_wider(names_from = PartyAbbreviation,
                  values_from = sum_value,
                  values_fill = 0)
    
    
    
    # Partei-Spaltennamen extrahieren (ohne Kanton/Jahr)
    partei_cols <- setdiff(names(df_wide),
                           c("CantonAbbreviation"))
    
    # Spaltensummen der Parteien berechnen für Sortierung
    partei_sums <- colSums(df_wide[partei_cols])
    
    # Zeilensumme der Kantone/Jahre hinzufügen
    df_wide$Total <- rowSums(df_wide[partei_cols])
    
    
    # Parteispalten-Anordnung nach Summe sortieren
    sorted_partei <- names(sort(partei_sums,
                                decreasing = TRUE))
    
    
    # Dataframe neu anordnen: Kanton/Jahr,  sortierte und gefilterte Parteien
    elec_nationalrat_final <- df_wide[, c(setdiff(names(df_wide),
                                                  partei_cols),
                                          sorted_partei)] %>% 
      arrange(desc(Total))
    
datatable(elec_nationalrat_final,
          filter = 'top',
          options = list(pageLength = 7,
          search = list(regex = TRUE,
                        caseInsensitive = TRUE)))

3.2.1.2 Ständerat

Code anzeigen
#TODO Es kann innerhalb der Legislatur Wechsel geben --> Anstelle der heute "Aktiven" ist es deshalb genauer die Räte/Konstellation zum Zeitpunkt der jeweiligen Abstimmung zu prüfen.


# Anzeige mit datatable()
datatable(elec_ständerat,
          filter = 'top',
          options = list(pageLength = 7,
          search = list(regex = TRUE,
                        caseInsensitive = TRUE)))
Code anzeigen
# Fehlende Werte (NA), Klassen und Levels prüfen
Abstract(elec_ständerat)
────────────────────────────────────────────────────────────────────────────── 
elec_ständerat

data frame: 155 obs. of  21 variables
        0 complete cases (0.0%)

  Nr  Class  ColName                NAs           Levels
  1   log    Active                   .                 
  2   chr    FirstName                .                 
  3   chr    LastName                 .                 
  4   chr    GenderAsString           .                 
  5   chr    CantonName               .                 
  6   chr    CantonAbbreviation       .                 
  7   chr    CouncilName              .                 
  8   chr    ParlGroupName            .                 
  9   chr    ParlGroupAbbreviation    .                 
  10  chr    PartyName                .                 
  11  chr    PartyAbbreviation        .                 
  12  chr    MaritalStatusText      116 (74.8%)         
  13  chr    Nationality              .                 
  14  chr    BirthPlace_City          .                 
  15  chr    BirthPlace_Canton        .                 
  16  chr    Mandates                11 (7.1%)          
  17  chr    DateJoining              .                 
  18  chr    DateLeaving             45 (29.0%)         
  19  chr    Citizenship              .                 
  20  chr    DateOfBirth              .                 
  21  log    DateOfDeath            155 (100.0%)        
Code anzeigen
PlotMiss(elec_ständerat)

Code anzeigen
# Datensatz reduzieren auf Aktive und doppelte Einträge elimnieren
elec_ständerat_reduced <- elec_ständerat %>%
  filter(Active == TRUE) %>% 
  distinct(.keep_all = TRUE) %>% 
  select(CantonAbbreviation,
         CantonName,
         PartyAbbreviation,
         PartyName) %>% 
  mutate(value = 1)

datatable(elec_ständerat_reduced,
          filter = 'top',
          options = list(pageLength = 7,
          search = list(regex = TRUE,
                        caseInsensitive = TRUE)))
Code anzeigen
# Sitze pro Kanton, Partei und Jahr 
    
    # long format
      df_sum <- elec_ständerat_reduced %>%
      group_by(CantonAbbreviation, PartyAbbreviation) %>%
      summarise(sum_value = sum(value, na.rm = TRUE),
                .groups = "drop")
    
    # wide format
    # Pivotieren: Partei-Spalten erzeugen
    df_wide <- df_sum %>%
      pivot_wider(names_from = PartyAbbreviation,
                  values_from = sum_value,
                  values_fill = 0)
    
    
    # Partei-Spaltennamen extrahieren (ohne Kanton/Jahr)
    partei_cols <- setdiff(names(df_wide),
                           c("CantonAbbreviation"))
    
    # Spaltensummen der Parteien berechnen für Sortierung
    partei_sums <- colSums(df_wide[partei_cols])
    
    # Zeilensumme der Kantone/Jahre hinzufügen
    df_wide$Total <- rowSums(df_wide[partei_cols])
    
    
    # Parteispalten-Anordnung nach Summe sortieren
    sorted_partei <- names(sort(partei_sums,
                                decreasing = TRUE))
    
    
    # Dataframe neu anordnen: Kanton/Jahr,  sortierte und gefilterte Parteien
    elec_ständerat_final <- df_wide[, c(setdiff(names(df_wide),
                                                  partei_cols),
                                        sorted_partei)] %>% 
      arrange(desc(Total))
    
datatable(elec_ständerat_final,
          filter = 'top',
          options = list(pageLength = 7,
          search = list(regex = TRUE,
                        caseInsensitive = TRUE)))

3.2.2 Kantonsebene

3.2.2.1 Kantonsparlament

3.2.2.2 Kantonsregierung

3.3 Parteilandschaft

3.3.1 Multi-Dimensions-Model

3.3.1.1 Minimalwerte

Für jede Partei den Minimalwert des Abstimmungsverhaltens zu verwenden, basiert auf der Zielsetzung, die klarste politische Position einer Partei in einem bestimmten Themenbereich zu identifizieren. Diese Methodik stellt sicher, dass auch bei wenigen Abweichungen von der Mehrheitslinie die tatsächliche Haltung der Partei deutlich erkennbar bleibt.

Code anzeigen
ggplot(parties_rating,
       aes(x = eco_pct_min_x,
           y = socio_pct_min_y,
           label = Partei)) +
  geom_point(size = 3) +
  geom_text(vjust = -0.8) +
  geom_segment(aes(x = 50, xend = 50,
                   y = 0, yend = 100), linetype = "dashed", color = "blue") +
  geom_segment(aes(x = 0, xend = 100,
                   y = 50, yend = 50), linetype = "dashed", color = "blue") +
  
  annotate("text",
           x = 0,
           y = min(parties_rating$socio_pct_min_y) + 55,
           label = "Links/staatsgläubig", size = 4, hjust = 0.5) +
  annotate("text",
           x = 100,
           y = min(parties_rating$socio_pct_min_y) + 55,
           label = "Rechts/marktwirtschaftlich", size = 4, hjust = 0.5) +
  annotate("text",
           x = 50,
           y = min(parties_rating$socio_pct_min_y) - 3,
           label = "Autoritär/Konservativ", size = 4, hjust = 0.5) +
  annotate("text",
           x = 50,
           y = min(parties_rating$socio_pct_min_y) + 103,
           label = "Libertär/Progressiv", size = 4, hjust = 0.5) +
  scale_x_continuous(limits = c(0, 100)) +
  labs(x = "Wirtschaftspolitisch",
       y = "Gesellschaftspolitisch",
       title = "Politische Positionierung im Zwei-Achsen-Modell",
       subtitle = "Grundlage: Minimalwerte des Abstimmungsverhaltens") +
  theme_minimal()+
  theme(plot.title = element_text(size = 18,
                                  margin = margin(b = 30)),
        axis.title.x = element_text(size = 14,
                                    margin = margin(t = 5)),
        axis.title.y = element_text(size = 14,
                                    margin = margin(r = 5))) +
  coord_cartesian(ylim = c(0, 105),
                  xlim = c(0, 105),
                  clip = "off")

3.3.1.2 Durchschnittswerte als Zentrum und Min/Max als Ellipse

Code anzeigen
ggplot(parties_rating,
       aes(x = eco_pct_avg_x,
           y = socio_pct_avg_y,
           label = Partei)) +
  geom_ellipse(
    aes(x0 = eco_pct_avg_x,
        y0 = socio_pct_avg_y,
        a = (eco_pct_max_x - eco_pct_min_x) / 2, # Halbachse x
        b = (socio_pct_max_y - socio_pct_min_y) / 2, # Halbachse y
        angle = 0),
    fill = "gray80", alpha = 0.4) +
  geom_point(size = 3) +
  geom_text(vjust = -0.8) +
  geom_segment(aes(x = 50, xend = 50,
                   y = 0, yend = 100), linetype = "dashed", color = "blue") +
  geom_segment(aes(x = 0, xend = 100,
                   y = 50, yend = 50), linetype = "dashed", color = "blue") +
  annotate("text",
           x = 0,
           y = min(parties_rating$socio_pct_min_y) + 55,
           label = "Links/staatsgläubig", size = 4, hjust = 0.5) +
  annotate("text",
           x = 100,
           y = min(parties_rating$socio_pct_min_y) + 55,
           label = "Rechts/marktwirtschaftlich", size = 4, hjust = 0.5) +
  annotate("text",
           x = 50,
           y = min(parties_rating$socio_pct_min_y) - 3,
           label = "Autoritär/Konservativ", size = 4, hjust = 0.5) +
  annotate("text",
           x = 50,
           y = min(parties_rating$socio_pct_min_y) + 103,
           label = "Libertär/Progressiv", size = 4, hjust = 0.5) +
  scale_x_continuous(limits = c(-10, +110)) +
  labs(x = "Wirtschaftspolitisch",
       y = "Gesellschaftspolitisch",
       title = "Politische Positionierung im Zwei-Achsen-Modell",
       subtitle = "Grundlage: Durchschnitt des Abstimmungsverhaltens als Zentrum, Min-/Max als Ellipse") +
  theme_minimal()+
  theme(plot.title = element_text(size = 18,
                                  margin = margin(b = 30)),
        axis.title.x = element_text(size = 14,
                                    margin = margin(t = 5)),
        axis.title.y = element_text(size = 14,
                                    margin = margin(r = 5))) +
  coord_cartesian(ylim = c(0, 100),
                  xlim = c(0, 100),
                  clip = "off")

3.3.1.3 Hauptparteien: Durchschnittswerte als Zentrum und Min/Max als Ellipse / Klein- und Regionalparteien nur mit ihrem Durchschnittswert

Code anzeigen
ggplot(parties_rating,
       aes(x = eco_pct_avg_x,
           y = socio_pct_avg_y,
           label = Partei)) +
  geom_ellipse(aes(x0 = eco_pct_avg_x,
                   y0 = socio_pct_avg_y,
                   a = (eco_pct_max_x - eco_pct_min_x) / 2, # Halbachse x
                   b = (socio_pct_max_y - socio_pct_min_y) / 2, # Halbachse y
                   angle = 0),
               fill = "gray80", alpha = 0.4) +
  geom_point(size = 3) +
  
  # Kleinere Parteien
  geom_point(data = subset(parties_rating,
                           `Grosse Partei` == "n" &
                             Relevanz_nationale_Abstimmungen != "-"),
             aes(x = eco_x,
                 y = socio_y),
             shape = 21, fill = "grey", color = "black", size = 1, stroke = 1) +
  
  # Labels der kleineren Partein
  geom_text(data = subset(parties_rating,
                          `Grosse Partei` == "n" &
                            Relevanz_nationale_Abstimmungen != "-"),
            aes(x = eco_x,
                y = socio_y,
                label = Partei),
            vjust = -1, fontface = "plain", color = "darkgrey") +
  geom_text(vjust = -0.8) +
  geom_segment(aes(x = 50, xend = 50,
                   y = 0, yend = 100), linetype = "dashed", color = "blue") +
  geom_segment(aes(x = 0, xend = 100,
                   y = 50, yend = 50), linetype = "dashed", color = "blue") +
  annotate("text",
           x = 0,
           y = min(parties_rating$socio_pct_min_y) + 55,
           label = "Links/staatsgläubig", size = 4, hjust = 0.5) +
  annotate("text",
           x = 100,
           y = min(parties_rating$socio_pct_min_y) + 55,
           label = "Rechts/marktwirtschaftlich", size = 4, hjust = 0.5) +
  annotate("text",
           x = 50,
           y = min(parties_rating$socio_pct_min_y) - 3,
           label = "Autoritär/Konservativ", size = 4, hjust = 0.5) +
  annotate("text",
           x = 50,
           y = min(parties_rating$socio_pct_min_y) + 103,
           label = "Libertär/Progressiv", size = 4, hjust = 0.5) +
  scale_x_continuous(limits = c(-10, +110)) +
  labs(x = "Wirtschaftspolitisch",
       y = "Gesellschaftspolitisch",
       title = "Politische Positionierung im Zwei-Achsen-Modell",
       subtitle = "Durchschnitt des Abstimmungsverhaltens als Zentrum, Min-/Max als Ellipse") +
  theme_minimal()+
  theme(plot.title = element_text(size = 18,
                                  margin = margin(b = 30)),
        axis.title.x = element_text(size = 14,
                                    margin = margin(t = 5)),
        axis.title.y = element_text(size = 14,
                                    margin = margin(r = 5))) +
  coord_cartesian(ylim = c(0, 100),
                  xlim = c(0, 100),
                  clip = "off")

3.3.1.4 Hauptparteien: Durchschnittswerte als Zentrum und Min/Max als Ellipse / Verbände sowie Klein- und Regionalparteien nur mit ihrem Durchschnittswert

Code anzeigen
ggplot(parties_rating,
       aes(x = eco_pct_avg_x,
           y = socio_pct_avg_y,
           label = Partei)) +
  geom_ellipse(
    aes(x0 = eco_pct_avg_x,
        y0 = socio_pct_avg_y,
        a = (eco_pct_max_x - eco_pct_min_x) / 2, # Halbachse x
        b = (socio_pct_max_y - socio_pct_min_y) / 2, # Halbachse y
        angle = 0),
    fill = "gray80", alpha = 0.4) +
  geom_point(size = 3) +
  
  # Kleinere Parteien
  geom_point(data = subset(parties_rating,
                           `Grosse Partei` == "n" #&
                           #Relevanz_nationale_Abstimmungen != "-"
                           )
                  ,
    aes(x = eco_x, y = socio_y),
    shape = 21, fill = "grey", color = "black", size = 1, stroke = 1
  ) +
  # Labels der kleineren Partein
  geom_text(data = subset(parties_rating,
                          `Grosse Partei` == "n" #&
                          #Relevanz_nationale_Abstimmungen != "-"
                  )
                  ,
    aes(x = eco_x, y = socio_y, label = Partei),
    vjust = -1, fontface = "plain", color = "darkgrey"
  ) +

  geom_text(vjust = -0.8) +
  geom_segment(aes(x = 50, xend = 50,
                   y = 0, yend = 100), linetype = "dashed", color = "blue") +
  geom_segment(aes(x = 0, xend = 100,
                   y = 50, yend = 50), linetype = "dashed", color = "blue") +
  annotate("text",
           x = 0,
           y = min(parties_rating$socio_pct_min_y) + 55,
           label = "Links/staatsgläubig", size = 4, hjust = 0.5) +
  annotate("text",
           x = 100,
           y = min(parties_rating$socio_pct_min_y) + 55,
           label = "Rechts/marktwirtschaftlich", size = 4, hjust = 0.5) +
  annotate("text",
           x = 50,
           y = min(parties_rating$socio_pct_min_y) - 3,
           label = "Autoritär/Konservativ", size = 4, hjust = 0.5) +
  annotate("text",
           x = 50,
           y = min(parties_rating$socio_pct_min_y) + 103,
           label = "Libertär/Progressiv", size = 4, hjust = 0.5) +
  scale_x_continuous(limits = c(-10, +110)) +
  labs(x = "Wirtschaftspolitisch",
       y = "Gesellschaftspolitisch",
       title = "Politische Positionierung im Zwei-Achsen-Modell",
       subtitle = "Grundlage: Durchschnitt des Abstimmungsverhaltens als Zentrum, Min-/Max als Ellipse") +
  theme_minimal()+
  theme(plot.title = element_text(size = 18,
                                  margin = margin(b = 30)),
        axis.title.x = element_text(size = 14,
                                    margin = margin(t = 5)),
        axis.title.y = element_text(size = 14,
                                    margin = margin(r = 5))) +
  coord_cartesian(ylim = c(0, 100),
                  xlim = c(0, 100),
                  clip = "off")

3.3.2 3D-Koordinatensystem (Testweise)

Code anzeigen
#install.packages("plotly")

library(plotly)

# Datenframe erstellen
parties <- data.frame(
  Partei = c("SP", "SVP", "FDP", "GPS", "GLP", "Mitte", "EVP", "EDU"),
  Wirtschaft = c(20, 85, 80, 30, 60, 60, 40, 90),      # Links = niedrig, Rechts = hoch
  Gesellschaft = c(90, 20, 55, 90, 75, 45, 50, 20),    # Libertär = hoch, Autoritär = niedrig
  Kosmopolitismus = c(90, 15, 60, 95, 85, 50, 55, 20)  # Kosmopolitisch = hoch, Nationalistisch = niedrig
)

# Interaktives 3D-Scatterplot mit Plotly
fig <- plot_ly(
  data = parties,
  x = ~Wirtschaft,
  y = ~Gesellschaft,
  z = ~Kosmopolitismus,
  text = ~Partei,
  type = 'scatter3d',
  mode = 'markers+text',
  marker = list(size = 5),
  textposition = 'top center'
)

# Achsen benennen und Layout anpassen
fig <- fig %>% layout(
  scene = list(
    xaxis = list(title = "Wirtschaftspolitik (links – rechts)"),
    yaxis = list(title = "Gesellschaftspolitik (autoritär – libertär)"),
    zaxis = list(title = "Kosmopolitismus – Nationalismus")
  ),
  title = "Schweizer Parteien im 3D-Koordinatensystem nach Kitschelt"
)

# Plot anzeigen
fig